package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"
)

type Setting struct {
	AccessPassword     string `json:"accessPassword"`
	FileAccessPassword string `json:"fileAccessPassword"`
	UseAccessPassword  bool   `json:"useAccessPassword"`
	DataPath           string `json:"dataPath"`
	Port               int    `json:"port"`
	// GitUser            string `json:"gitUser"`
	// GitToken           string `json:"gitToken"`
}

type SortType string

const (
	SortByCreateTime SortType = "1"
	SortByModTime    SortType = "2"
)

// ======================================================================

var CurrentSetting *Setting

func init() {
	var err error
	CurrentSetting, err = ReadSetting()
	if err != nil {
		log.Fatalf("fail: %v", err.Error())
	}
	initDataPath()
	initImagePath()
}

// 获取设置
func settings(rsp http.ResponseWriter, _ *http.Request) {
	rsp.Header().Set("content-type", "text/json")
	s, err := ReadSetting()
	if err != nil {
		errResult, _ := json.Marshal(Result{Success: false, Msg: "读取配置失败," + err.Error()})
		rsp.Write(errResult)
		return
	}
	result := Result{Success: true, Msg: "ok", Data: s}
	bs, _ := json.Marshal(result)
	rsp.Write(bs)
}

// 保存设置
func saveSettings(rsp http.ResponseWriter, req *http.Request) {
	bs, err := io.ReadAll(req.Body)
	if err != nil {
		errResult, _ := json.Marshal(Result{Success: false, Msg: err.Error()})
		rsp.Write(errResult)
		return
	}
	var setting *Setting
	err = json.Unmarshal(bs, &setting)
	if err != nil {
		errResult, _ := json.Marshal(Result{Success: false, Msg: "参数错误," + err.Error()})
		rsp.Write(errResult)
		return
	}
	err = WriteSetting(bs)
	if err != nil {
		errResult, _ := json.Marshal(Result{Success: false, Msg: "参数错误," + err.Error()})
		rsp.Write(errResult)
		return
	}
}

type Item struct {
	IsDir      bool   `json:"isDir"`
	Label      string `json:"label"`
	Key        string `json:"key"`
	Id         string `json:"id"`
	Children   []Item `json:"children"`
	ModTime    string `json:"modTime"`
	CreateTime string `json:"createTime"`
}

func updateFileIndex(path string) {
	FileIndex[path] = readFileToIndexMap(path)
}
func listFile(items []Item) {
	for _, v := range items {
		if !v.IsDir {
			//FileIndex[v.Key] = readFileToIndexMap(v.Key)
			updateFileIndex(v.Key)
		}
		if len(v.Children) > 0 {
			listFile(v.Children)
		}
	}
}
func readFileToIndexMap(key string) string {
	file, err := os.OpenFile(key, os.O_RDONLY, os.ModePerm)
	if err != nil {
		panic(err)
	}
	defer file.Close()
	bs, err := io.ReadAll(file)
	if err != nil {
		panic(err)
	}
	return removeHTMLTags(string(bs))
}

func removeHTMLTags(html string) string {
	re := regexp.MustCompile(`<[^>]*>`)
	return re.ReplaceAllString(html, "")
}

// 构建文件内容索引
func buildFileContentIndex() {
	parentPath := CurrentSetting.DataPath
	dirs, err := readDir(parentPath, SortType("1"), false, false)
	if err != nil {
		log.Panic(err)
	}
	for _, v := range dirs {
		if StartWith(v.Label, ".") {
			continue
		}
		if !v.IsDir {
			FileIndex[v.Key] = readFileToIndexMap(v.Key)
		}
		if len(v.Children) > 0 {
			listFile(v.Children)
		}
	}
}

// 目录列表
func itemsList(rsp http.ResponseWriter, req *http.Request) {
	rsp.Header().Set("content-type", "text/json")

	parentPath := req.URL.Query().Get("path")
	if parentPath == "" {
		parentPath = CurrentSetting.DataPath
	}
	var showHidden bool = false
	showHiddenFromQuery := req.URL.Query().Get("showHidden")
	if showHiddenFromQuery == "1" {
		showHidden = true
	}
	sortTypeFromQuery := req.URL.Query().Get("sort") // 1 creationTime， 2 modTime
	descFromQuery := req.URL.Query().Get("desc")     // 1 desc , others asc

	dirs, err := readDir(parentPath, SortType(sortTypeFromQuery), descFromQuery == "desc", showHidden)
	if err != nil {
		errResult, _ := json.Marshal(Result{Success: false, Msg: "读取目录错误," + err.Error()})
		rsp.Write(errResult)
		return
	}
	res, _ := json.Marshal(Result{Success: true, Msg: "ok", Data: dirs})
	rsp.Write(res)
}

type SearchResult struct {
	FilePath string `json:"filePath"`
	Context  string `json:"context"` // 前后50字内容
}

// 文件搜索
func handleSearchFile(rsp http.ResponseWriter, req *http.Request) {
	rsp.Header().Set("content-type", "text/json")
	keyword := strings.TrimSpace(req.URL.Query().Get("keyword"))
	result := []SearchResult{}

	for k, v := range FileIndex {
		startIndex := 0
		endIndex := 50
		if strings.Contains(v, keyword) {
			index := strings.Index(v, keyword)
			if index-50 > 0 {
				startIndex = index - 50
			}
			if index+endIndex > len(v) {
				endIndex = len(v)
			} else {
				endIndex = index + endIndex
			}

			result = append(result, SearchResult{
				FilePath: k,
				Context:  v[startIndex:endIndex],
			})
		}
	}
	Ok(&rsp, result)
}

func readDir(p string, sortType SortType, desc bool, showHidden bool) ([]Item, error) {

	fis, err := os.ReadDir(p)
	if err != nil {
		return nil, err
	}
	var dirs []Item = []Item{}
	for _, v := range fis {
		if !showHidden && StartWith(v.Name(), ".") {
			continue
		}
		if v.IsDir() && v.Name() == ImageDirName {
			continue
		}
		obsPath := path.Join(p, v.Name())
		var childs []Item = nil
		if v.IsDir() {
			childs, err = readDir(obsPath, sortType, desc, showHidden)
			if err != nil {
				return nil, err
			}
		}
		i := Item{IsDir: v.IsDir(), Label: v.Name(), Key: obsPath, Id: obsPath, Children: childs}
		// 获取创建修改时间
		filePath := filepath.Join(p, v.Name())
		info, err := os.Stat(filePath)
		if err == nil {
			i.ModTime = FormatDateTime(info.ModTime())
			i.CreateTime = FormatDateTime(GetCreationTime(info))
		}

		dirs = append(dirs, i)
	}
	// switch sortType {
	// case SortByCreateTime:
	// 	sort.Slice(dirs, func(i, j int) bool {
	// 		if desc {
	// 			return dirs[i].CreateTime.Before(dirs[j].CreateTime)
	// 		} else {
	// 			return dirs[i].CreateTime.After(dirs[j].CreateTime)
	// 		}
	// 	})
	// case SortByModTime:
	// 	sort.Slice(dirs, func(i, j int) bool {
	// 		if desc {
	// 			return dirs[i].ModTime.Before(dirs[j].CreateTime)
	// 		} else {
	// 			return dirs[i].ModTime.After(dirs[j].CreateTime)
	// 		}
	// 	})
	// default:

	// }
	return dirs, nil
}

type RenameItemReq struct {
	ParentPath string `json:"parentPath"`
	OldName    string `json:"oldName"`
	NewName    string `json:"newName"`
	IsDir      bool   `json:"isDir"`
}

// 重命名或创建文件/文件夹
func renameItem(rsp http.ResponseWriter, req *http.Request) {
	rsp.Header().Set("content-type", "text/json")
	var reqObj RenameItemReq
	bs, err := io.ReadAll(req.Body)
	if err != nil {
		Fail(&rsp, err.Error())
		return
	}
	err = json.Unmarshal(bs, &reqObj)
	if err != nil {
		Fail(&rsp, err.Error())
		return
	}
	if reqObj.ParentPath == "" {
		reqObj.ParentPath = CurrentSetting.DataPath
	}
	oldPath := path.Join(reqObj.ParentPath, reqObj.OldName)
	newPath := path.Join(reqObj.ParentPath, reqObj.NewName)

	if reqObj.OldName == "" && reqObj.NewName != "" {
		if reqObj.IsDir {
			// 新建文件夹
			err := CreateFolder(newPath)
			if err != nil {
				Fail(&rsp, err.Error())
				return
			}
		} else {
			// 新建文件
			err := CreateFile(newPath)
			if err != nil {
				Fail(&rsp, err.Error())
				return
			}
		}
	} else {

		// 修改
		err := RenameItem(oldPath, newPath)
		if err != nil {
			Fail(&rsp, err.Error())
			return
		}
	}
	if reqObj.IsDir {
		Ok(&rsp, nil)
	} else {
		Ok(&rsp, newPath)
	}
	go updateFileIndex(newPath)
}

func deleteItem(rsp http.ResponseWriter, req *http.Request) {
	rsp.Header().Set("content-type", "text/json")
	q := req.URL.Query()
	pathName := strings.TrimSpace(q.Get("path"))

	if pathName != "" {
		if pathName == path.Join(CurrentSetting.DataPath, ImageDirName) {
			Fail(&rsp, "图片目录请手动删除")
			return
		}
		fileinfo, err := os.Stat(path.Join(CurrentSetting.DataPath))
		if err != nil {
			if os.IsNotExist(err) {
				Fail(&rsp, "文件或目录不存在")
				return
			} else {
				Fail(&rsp, err.Error())
				return
			}
		}
		err = DeleteItem(pathName, fileinfo.IsDir())
		if err != nil {
			Fail(&rsp, err.Error())
			return
		}
	}
	Ok(&rsp, nil)
}

type FileDetail struct {
	Content   string `json:"content"`
	IsEncrypt bool   `json:"isEncrypt"`
}

func fileContent(rsp http.ResponseWriter, req *http.Request) {
	rsp.Header().Set("content-type", "text/json")
	filePath := req.URL.Query().Get("path")
	file, err := os.OpenFile(filePath, os.O_RDONLY, os.ModePerm)
	if err != nil {
		Fail(&rsp, err.Error())
		return
	}
	defer file.Close()
	bs, err := io.ReadAll(file)
	if err != nil {
		Fail(&rsp, err.Error())
		return
	}
	contentStr := string(bs)
	result := FileDetail{IsEncrypt: false}
	if contentStr != "" && !StartWith(strings.TrimSpace(contentStr), "<") {
		result.IsEncrypt = true
	} else {
		result.Content = contentStr
	}
	Ok(&rsp, result)
}

func updateFileContent(rsp http.ResponseWriter, req *http.Request) {
	rsp.Header().Set("content-type", "text/json")
	filePath := req.URL.Query().Get("path")
	if err := req.ParseMultipartForm(10 * 1024); err != nil {
		Fail(&rsp, err.Error())
		return
	}
	content := req.PostFormValue("content")
	file, err := os.OpenFile(filePath, os.O_RDWR|os.O_TRUNC, os.ModePerm)
	if err != nil {
		Fail(&rsp, err.Error())
		return
	}
	defer file.Close()
	_, err = io.WriteString(file, content)
	if err != nil {
		Fail(&rsp, err.Error())
		return
	}
	// 更新索引
	FileIndex[filePath] = removeHTMLTags(content)
	Ok(&rsp, nil)
}

// 加密文件内容
func encryptFileContent(rsp http.ResponseWriter, req *http.Request) {
	rsp.Header().Set("content-type", "text/json")
	filePath := req.URL.Query().Get("path")
	file, err := os.OpenFile(filePath, os.O_RDONLY, os.ModePerm)
	if err != nil {
		Fail(&rsp, err.Error())
		return
	}
	bs, err := io.ReadAll(file)
	if err != nil {
		Fail(&rsp, err.Error())
		return
	}

	encryptedContent, err := Encrypt([]byte(CurrentSetting.FileAccessPassword), bs)
	if err != nil {
		Fail(&rsp, err.Error())
		return
	}
	file.Close()
	file, err = os.OpenFile(filePath, os.O_RDWR|os.O_TRUNC, os.ModePerm)
	if err != nil {
		Fail(&rsp, err.Error())
		return
	}
	_, err = io.WriteString(file, encryptedContent)
	if err != nil {
		Fail(&rsp, err.Error())
		return
	}
	defer file.Close()
	go updateFileIndex(filePath)
	Ok(&rsp, nil)
}

// 解密文件内容
func decryptFileContent(rsp http.ResponseWriter, req *http.Request) {
	rsp.Header().Set("content-type", "text/json")
	filePath := req.URL.Query().Get("path")

	file, err := os.OpenFile(filePath, os.O_RDWR, os.ModePerm)
	if err != nil {
		Fail(&rsp, err.Error())
		return
	}
	bs, err := io.ReadAll(file)
	if err != nil {
		Fail(&rsp, err.Error())
		return
	}

	encryptedContent := string(bs)
	content, err := Decrypt([]byte(CurrentSetting.FileAccessPassword), encryptedContent)
	if err != nil {
		Fail(&rsp, err.Error())
		return
	}
	file.Close()
	file, err = os.OpenFile(filePath, os.O_RDWR|os.O_TRUNC, os.ModePerm)
	if err != nil {
		Fail(&rsp, err.Error())
		return
	}
	_, err = io.WriteString(file, content)
	if err != nil {
		Fail(&rsp, err.Error())
		return
	}
	defer file.Close()
	go updateFileIndex(filePath)
	Ok(&rsp, nil)
}

func deleteImage(rsp http.ResponseWriter, req *http.Request) {
	rsp.Header().Set("content-type", "text/json")
	q := req.URL.Query()
	filename := strings.TrimSpace(q.Get("path"))

	if filename != "" {
		filePath := path.Join(CurrentSetting.DataPath, ImageDirName, filename)
		fileinfo, err := os.Stat(filePath)
		if err != nil {
			if os.IsNotExist(err) {
				Fail(&rsp, "文件或目录不存在")
				return
			} else {
				Fail(&rsp, err.Error())
				return
			}
		}
		err = DeleteItem(filePath, fileinfo.IsDir())
		if err != nil {
			Fail(&rsp, err.Error())
			return
		}
	}
	Ok(&rsp, nil)
}

func uploadHandler(rsp http.ResponseWriter, req *http.Request) {
	file, fileHeader, err := req.FormFile("image")
	if err != nil {
		Fail(&rsp, "参数错误,"+err.Error())
		return
	}
	imageDirPath := path.Join(CurrentSetting.DataPath, ImageDirName)
	timestamp := strconv.FormatInt(time.Now().UnixMilli(), 10)
	fileName := timestamp + "_" + fileHeader.Filename
	imageFilePath := path.Join(imageDirPath, fileName)
	saveFile, err := os.OpenFile(imageFilePath, os.O_CREATE|os.O_WRONLY, os.ModePerm)
	if err != nil {
		Fail(&rsp, "图片文件创建失败,"+err.Error())
		return
	}
	_, err = io.Copy(saveFile, file)
	if err != nil {
		Fail(&rsp, "图片保存失败,"+err.Error())
		return
	}
	res, _ := json.Marshal(Result{Success: true, Msg: "ok", Data: fmt.Sprintf("http://localhost:%v/images/%v", CurrentSetting.Port, fileName)})
	rsp.Write(res)
}

// 拉取
func pullFromGit(rsp http.ResponseWriter, req *http.Request) {
	cmd := exec.Command("git", "pull")
	cmd.Dir = CurrentSetting.DataPath
	var stdout, stderr bytes.Buffer
	cmd.Stderr = &stderr
	cmd.Stdout = &stdout
	err := cmd.Run()
	if err != nil {
		errStr := stderr.String()
		Fail(&rsp, errStr)
		return
	}
	go buildFileContentIndex() // 重建索引
	outStr := stdout.String()
	Ok(&rsp, outStr)
}
func commitAll(rsp http.ResponseWriter, req *http.Request) {
	comment := req.URL.Query().Get("comment")
	if comment == "" {
		comment = FormatDateTime(time.Now())
	}
	var stdout, stderr bytes.Buffer
	cmd := exec.Command("git", "add", ".")
	cmd.Dir = CurrentSetting.DataPath
	err := cmd.Run()
	cmd.Stderr = &stderr
	cmd.Stdout = &stdout
	if err != nil {
		msg := stderr.String()
		if msg == "" {
			msg = stdout.String()
		}
		Fail(&rsp, msg+" "+err.Error())
		return
	}
	stdout.Reset()
	stderr.Reset()

	cmd = exec.Command("git", "commit", "-am", comment)
	cmd.Dir = CurrentSetting.DataPath
	err = cmd.Run()
	cmd.Stderr = &stderr
	cmd.Stdout = &stdout
	if err != nil {
		msg := stderr.String()
		if msg == "" {
			msg = stdout.String()
		}
		Fail(&rsp, msg+" "+err.Error())
		return
	}
	Ok(&rsp, nil)
}
func pushGit(rsp http.ResponseWriter, req *http.Request) {
	var stdout, stderr bytes.Buffer
	cmd := exec.Command("git", "push")
	cmd.Dir = CurrentSetting.DataPath
	cmd.Stderr = &stderr
	cmd.Stdout = &stdout
	err := cmd.Run()
	if err != nil {
		errStr := stderr.String()
		Fail(&rsp, errStr)
		return
	}
	outStr := stdout.String()
	Ok(&rsp, outStr)
}

type GitStatusResult struct {
	Push   int `json:"push"`
	Commit int `json:"commit"`
}

func gitStatus(rsp http.ResponseWriter, req *http.Request) {

	var stdout, stderr bytes.Buffer
	cmd := exec.Command("git", "status")
	cmd.Dir = CurrentSetting.DataPath
	cmd.Stderr = &stderr
	cmd.Stdout = &stdout
	err := cmd.Run()
	if err != nil {
		errStr := stderr.String()
		Fail(&rsp, errStr)
		return
	}

	outStr := stdout.String()
	result := GitStatusResult{Push: 0, Commit: 0}
	if strings.Contains(outStr, "git add") {
		result.Commit = 1
	}
	if strings.Contains(outStr, "git push") {
		result.Push = 1
	}
	Ok(&rsp, result)
}
